#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "check.h"
#include "ieee1284.h"
#include "stringprintf.h"


const char *ieee1284Id(const char *dev) {
  struct stat sb;
  int fd = -1;
  if (!stat(dev, &sb) && S_ISCHR(sb.st_mode)) {
    int devMajor = major(sb.st_rdev);
    int devMinor = minor(sb.st_rdev);
    if (devMajor == 6) {
      // This is an actual parallel port device (e.g. /dev/lp0). It has
      // associated information in /proc/sys/dev/parport/parport$DEVNUM
      char *autoprobe = stringPrintf(NULL,
                      "/proc/sys/dev/parport/parport%d/autoprobe", devMinor);
      fd = open(autoprobe, O_RDONLY);
    } else {
      // Maybe, this is a USB printer device. Look in /sys/bus/usb/devices/*
      // and find a ".../usb/*/dev" file that matches our major and minor
      // device nodes.
      DIR *dir0;
      static const char devices[] = "/sys/bus/usb/devices";
      if ((dir0 = opendir(devices)) != NULL) {
        char buf0[offsetof(struct dirent, d_name) + PATH_MAX + 1];
        struct dirent *entry0;
        while (fd < 0 &&
               !readdir_r(dir0, (struct dirent *)buf0, &entry0) && entry0) {
          if (*entry0->d_name != '.' &&
              (entry0->d_type == DT_DIR || entry0->d_type == DT_LNK)) {
            char *fname = stringPrintf(NULL, "%s/%s/usb", devices,
                                       entry0->d_name);
            DIR *dir1;
            if ((dir1 = opendir(fname)) != NULL) {
              char buf1[offsetof(struct dirent, d_name) + PATH_MAX + 1];
              struct dirent *entry1;
              while (fd < 0 &&
                     !readdir_r(dir1, (struct dirent *)buf1, &entry1) &&
                     entry1) {
                if (*entry1->d_name != '.' &&
                    (entry1->d_type == DT_DIR || entry1->d_type == DT_LNK)) {
                  int oldLen = strlen(fname);
                  fname = stringPrintf(fname, "/%s/dev", entry1->d_name);
                  fd = TEMP_FAILURE_RETRY(open(fname, O_RDONLY));
                  fname[oldLen] = '\000';
                  if (fd >= 0) {
                    char buf2[80];
                    ssize_t rc;
                    if ((rc = TEMP_FAILURE_RETRY(read(fd, buf2,
                                                      sizeof(buf2)-1))) > 0){
                      buf2[rc] = '\000';
                      int usbMajor, usbMinor;
                      if (sscanf(buf2, "%d:%d", &usbMajor, &usbMinor) == 2 &&
                          usbMajor == devMajor && usbMinor == devMinor) {
                        check(!TEMP_FAILURE_RETRY(close(fd)));
                        *strrchr(fname, '/') = '\000';
                        stringPrintf(fname, "/ieee1284_id");
                        fd = open(fname, O_RDONLY);
                        break;
                      }
                    }
                    check(!TEMP_FAILURE_RETRY(close(fd)));
                    fd = -1;
                  }
                }
              }
              closedir(dir1);
            }
            free(fname);
          }
        }
        closedir(dir0);
      }
    }
  }

  // If we managed to find a file that contains the IEEE1284 id string, read
  // the entire file and return the contents. Otherwise, return an empty
  // string.
  char *buf;
  check(buf = strdup(""));
  if (fd > 0) {
    size_t sz  = 0;
    size_t len = 0;
    for (;;) {
      if (len >= sz) {
        check(buf = realloc(buf, (sz = len + 4096) + 1));
      }
      ssize_t rc = TEMP_FAILURE_RETRY(read(fd, buf + len, sz - len));
      if (rc <= 0) {
        break;
      }
      for (char *ptr = buf + len; ptr < buf + len + rc; ++ptr) {
        // IEEE1284 strings have all sorts of strange data in them; they
        // might even contain NUL characters. Replace those with spaces.
        if (!*ptr) {
          *ptr = ' ';
        }
      }
      len += rc;
    }
    buf[len] = '\000';
    check(buf = realloc(buf, len + 1));
    check(!TEMP_FAILURE_RETRY(close(fd)));
  }
  return buf;
}

const char *ieee1284Model(const char *dev) {
  const char *id = ieee1284Id(dev);
  const char *ptr = strstr(id, "MODEL:");
  if (!ptr) {
    ptr = strstr(id, "MDL:");
    if (ptr) {
      ptr += 4;
    }
  } else {
    ptr += 6;
  }
  if (ptr) {
    size_t sz = strcspn(ptr, ";");
    if (sz > 0) {
      char *buf;
      check(buf = malloc(sz + 1));
      memcpy(buf, ptr, sz);
      buf[sz] = '\000';
      free((void *)id);
      return buf;
    }
  }
  free((void *)id);
  return NULL;
}
